/**
 *
 */
package com.tongzhan.base;

import cn.minsin.core.exception.MutilsException;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.tongzhan.annotation.Exclude;
import com.tongzhan.annotation.LogicDelete;
import com.tongzhan.annotation.PrimaryKey;
import com.tongzhan.utils.StringUtilPlus;
import org.bson.Document;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author mintonzhang 2018年10月15日
 */
public class BaseRepository<T, ID> extends SimpleMongoRepository<T, ID> implements IBaseRepository<T, ID> {

    private final MongoOperations mongotemplate;
    private final MongoEntityInformation<T, ID> entityInformation;

    public BaseRepository(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
        super(metadata, mongoOperations);
        this.mongotemplate = mongoOperations;
        this.entityInformation = metadata;
    }

    @Override
    public long updateOne(Query query, Update update) {
        UpdateResult updateFirst = mongotemplate.updateFirst(query, update, entityInformation.getJavaType());
        return updateFirst.getModifiedCount();
    }

    @Override
    public long updateMulti(Query query, Update update) {
        UpdateResult updateMulti = mongotemplate.updateMulti(query, update, entityInformation.getJavaType());
        return updateMulti.getModifiedCount();
    }

    @Override
    public MongoTemplate getMongotemplate() {
        return (MongoTemplate) mongotemplate;
    }

    @Override
    public long delete(Query query) {
        DeleteResult remove = mongotemplate.remove(query, entityInformation.getJavaType());
        return remove.getDeletedCount();
    }

    @Override
    public long deletePrimaryKey(String value) {
        Class<T> javaType = entityInformation.getJavaType();
        String id = null;
        Field[] fields = javaType.getDeclaredFields();
        for (Field field : fields) {
            PrimaryKey annotation = field.getAnnotation(PrimaryKey.class);
            if (annotation != null) {
                id = field.getName();
                break;
            }
        }
        return mongotemplate.remove(new Query(BaseCriteria.where(id).is(value)), javaType).getDeletedCount();
    }

    @SuppressWarnings({"rawtypes"})
    @Override
    public List<Object> findSingleField(Criteria query, String field) {
        final List<Object> obj = new ArrayList<>();
        try {
            BasicQuery basicQuery = new BasicQuery(query == null ? new Document() : query.getCriteriaObject(),
                    new Document().append(field, true).append("_id", false));
            List<Map> find = mongotemplate.find(basicQuery, Map.class, entityInformation.getCollectionName());
            find.forEach(e -> {
                Object object = e.get(field);
                if (object != null) {
                    obj.add(object);
                }
            });
        } catch (Exception e) {
            throw new MutilsException(e);
        }
        return obj;
    }

    @Override
    public List<T> findFields(Criteria query, String... field) {
        List<T> list = new ArrayList<>();
        try {
            Document doc = new Document();
            doc.append("_id", false);
            for (String s : field) doc.append(s, true);
            BasicQuery basicQuery = new BasicQuery(query == null ? new Document() : query.getCriteriaObject(), doc);
            list = mongotemplate.find(basicQuery, entityInformation.getJavaType(), entityInformation.getCollectionName());
        } catch (Exception e) {
            throw new MutilsException(e);
        }
        return list;
    }

    @Override
    public List<T> findAll(Query query) {
        List<T> list;
        Class<T> javaType = entityInformation.getJavaType();
        List<T> objList = mongotemplate.find(query, javaType);
        if (CollectionUtils.isEmpty(objList)) {
            list = new ArrayList<>();
        } else {
            list = objList;
        }
        return list;
    }

    @SuppressWarnings({"rawtypes"})
    @Override
    public List<Object> findSingleQuery(Query query, String field) {
        List<Object> obj = new ArrayList<>();
        try {
            List<Map> find = mongotemplate.find(
                    new BasicQuery(query == null ? new Document() : query.getQueryObject(),
                            new Document().append(field, true).append("_id", false)),
                    Map.class, entityInformation.getCollectionName());
            find.forEach(e -> {
                Object object = e.get(field);
                if (object != null) {
                    obj.add(object);
                }
            });
        } catch (Exception e) {
            throw new MutilsException(e);
        }
        return obj;
    }

    @Override
    public Page<T> findAll(Query query, PageRequest pageRequest) {
        Pageable pageable = pageRequest == null ? Pageable.unpaged() : pageRequest;
        query.with(pageable);
        List<T> list = mongotemplate.find(query, entityInformation.getJavaType(),
                entityInformation.getCollectionName());
        return PageableExecutionUtils.getPage(list, pageable, () -> mongotemplate.count(query,
                entityInformation.getJavaType(), entityInformation.getCollectionName()));
    }

    @Override
    public Page<T> findPage(Query query, PageRequest pageRequest) {
        Pageable pageable = pageRequest == null ? Pageable.unpaged() : pageRequest;
        long count = mongotemplate.count(query,
                entityInformation.getJavaType(), entityInformation.getCollectionName());
        query.with(pageable);
        List<T> list = mongotemplate.find(query, entityInformation.getJavaType(),
                entityInformation.getCollectionName());
        return PageableExecutionUtils.getPage(list, pageable, () -> count);
    }

    @Override
    public T findById(String id, Object value) {
        return mongotemplate.findOne(new Query(BaseCriteria.where(id).is(value)), entityInformation.getJavaType());
    }

    /**
     * 按照@primaryKey列进行查询数据
     */
    @Override
    public T findByPrimaryKey(String value) {
        Class<T> javaType = entityInformation.getJavaType();
        String id = null;
        Field[] fields = javaType.getDeclaredFields();
        for (Field field : fields) {
            PrimaryKey annotation = field.getAnnotation(PrimaryKey.class);
            if (annotation != null) {
                id = field.getName();
                break;
            }
        }
        return mongotemplate.findOne(new Query(BaseCriteria.where(id).is(value)), javaType);
    }

    @Override
    public T findOne(Query query) {
        return mongotemplate.findOne(query, entityInformation.getJavaType());
    }

    @Override
    public long count(Query query) {
        return mongotemplate.count(query, entityInformation.getJavaType());
    }

    @Override
    public List<T> find(Query query, PageRequest pageRequest) {
        Pageable pageable = pageRequest == null ? Pageable.unpaged() : pageRequest;
        query.with(pageable);
        return mongotemplate.find(query, entityInformation.getJavaType(), entityInformation.getCollectionName());
    }

    @Override
    public Page<T> buliderPage(List<T> list, Query query, PageRequest pageRequest) {
        Pageable pageable = pageRequest == null ? Pageable.unpaged() : pageRequest;
        query.with(pageable);
        return PageableExecutionUtils.getPage(list, pageable, () -> mongotemplate.count(query,
                entityInformation.getJavaType(), entityInformation.getCollectionName()));
    }

    @Override
    public long insertSelective(T model) {
        try {
            Document document = new Document();
            Field[] fields = model.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (isExcludeField(field)) {
                    continue;
                }
                String key = field.getName();
                field.setAccessible(true);
                Object value = field.get(model);
                LogicDelete delete = field.getAnnotation(LogicDelete.class);
                if (delete != null) {
                    value = delete.valid();
                }

                PrimaryKey annotation = field.getAnnotation(PrimaryKey.class);
                if (annotation != null) {
                    if (key == null || value == null) {
                        throw new RuntimeException("Id列的值或键不能为空 --from insertSelective");
                    }
                    document.put("_id", value);
                    document.put(key, value);
                } else {
                    if (StringUtilPlus.checkMissParam(value)) {
                        value = null;
                    } else if (value instanceof Date) {
                    }
                    document.put(key, value);
                }
            }
            mongotemplate.insert(document, entityInformation.getCollectionName());
            return 1;
        } catch (Exception e) {
            throw new MutilsException(e);
        }
    }

    @Override
    public long updateByPrimaryKeySelective(T model) {
        try {
            Object idValue = null;
            String idkey = null;
            Field[] fields = model.getClass().getDeclaredFields();
            Update update = new Update();
            for (Field field : fields) {
                if (isExcludeField(field)) {
                    continue;
                }
                LogicDelete delete = field.getAnnotation(LogicDelete.class);

                PrimaryKey annotation = field.getAnnotation(PrimaryKey.class);

                String key = field.getName();
                //不能修改_id列
                boolean pass = ("_id".equals(key) && annotation == null) || delete != null;
                if (!pass) {
                    field.setAccessible(true);
                    Object value = field.get(model);
                    if (annotation != null && idValue == null && idkey == null) {
                        if (StringUtilPlus.checkMissParam(value)) {
                            throw new Exception("@PrimaryKey的值为空");
                        }
                        idValue = value;
                        idkey = key;
                    } else {
                        if (StringUtilPlus.checkMissParam(value))
                            continue;
                        update.set(key, value);
                    }
                }
            }
            UpdateResult updateFirst = mongotemplate.updateFirst(new Query(BaseCriteria.where(idkey).is(idValue)),
                    update, entityInformation.getJavaType());
            return updateFirst.getModifiedCount();

        } catch (Exception e) {
            throw new MutilsException(e);
        }
    }

    @Override
    public long updateByPrimaryKey(T model) {
        try {
            Object idValue = null;
            String idkey = null;
            Field[] fields = model.getClass().getDeclaredFields();
            Update update = new Update();
            for (Field field : fields) {
                if (isExcludeField(field)) {
                    continue;
                }
                LogicDelete delete = field.getAnnotation(LogicDelete.class);

                PrimaryKey annotation = field.getAnnotation(PrimaryKey.class);

                String key = field.getName();
                //不能修改_id列
                boolean pass = ("_id".equals(key) && annotation == null) || delete != null;
                if (!pass) {
                    field.setAccessible(true);
                    Object value = field.get(model);
                    if (annotation != null && idValue == null && idkey == null) {
                        if (StringUtilPlus.checkMissParam(value)) {
                            throw new Exception("@PrimaryKey的值为空");
                        }
                        idValue = value;
                        idkey = key;
                    } else {
                        update.set(key, value);
                    }
                }
            }
            UpdateResult updateFirst = mongotemplate.updateFirst(new Query(BaseCriteria.where(idkey).is(idValue)),
                    update, entityInformation.getJavaType());
            return updateFirst.getModifiedCount();

        } catch (Exception e) {
            throw new MutilsException(e);
        }
    }

    @Override
    public long logicDelete(T model) {
        try {
            Field[] fields = model.getClass().getDeclaredFields();

            Field idField = null;
            Field delField = null;
            Object invalid = 1;
            for (Field field : fields) {
                if (isExcludeField(field)) {
                    continue;
                }
                PrimaryKey annotation = field.getAnnotation(PrimaryKey.class);
                if (annotation != null) {
                    idField = field;
                    continue;
                }
                LogicDelete delete = field.getAnnotation(LogicDelete.class);
                if (delete != null) {
                    delField = field;
                    invalid = delete.invalid();
                    continue;
                }
                if (idField != null && delField != null) {
                    break;
                }
            }
            //获取值
            idField.setAccessible(true);
            Object idValue = idField.get(model);
            if (idValue != null) {
                Update update = new Update();
                update.set(delField.getName(), invalid);
                UpdateResult updateFirst = mongotemplate.updateFirst(new Query(BaseCriteria.where(idField.getName()).is(idValue)),
                        update, entityInformation.getJavaType());
                return updateFirst.getModifiedCount();
            }
            return 0;
        } catch (Exception e) {
            throw new MutilsException(e);
        }
    }

    @SuppressWarnings("unchecked")
	@Override
    public <O> O group(Criteria match, String key, String value, Class<O> output, GroupType type) {
        Aggregation newAggregation = Aggregation.newAggregation(
                Aggregation.match(match),
                this.groupOperation(key, value, type));
        AggregationResults<Document> aggregationResults = mongotemplate.aggregate(newAggregation, entityInformation.getCollectionName(), Document.class);
        //  获取结果集
        List<Document> documentList = aggregationResults.getMappedResults();
        if (!CollectionUtils.isEmpty(documentList)) {
            // 2.3 获取结果的第一个结果
            return (O) documentList.get(0).get("r");
        }
        return null;
    }

    @Override
    public List<Document> groupCount(Criteria match, String key){
        Aggregation newAggregation = Aggregation.newAggregation(
                Aggregation.match(match),
                this.groupOperation(key, "_id", GroupType.COUNT));
        AggregationResults<Document> aggregationResults = mongotemplate.aggregate(newAggregation, entityInformation.getCollectionName(), Document.class);
        //  获取结果集
        List<Document> documentList = aggregationResults.getMappedResults();
        return documentList;
    }

    private boolean isExcludeField(Field field) {
        int modifiers = field.getModifiers();
        Exclude exclude = field.getAnnotation(Exclude.class);
        if (exclude != null || Modifier.isStatic(modifiers) || !Modifier.isPrivate(modifiers)) {
            return true;
        }
        return false;
    }

    private GroupOperation groupOperation(String key1, String key2, GroupType type) {
        Assert.notNull(type, "GroupType 不能为空");
        switch (type) {
            case AVG:
                return Aggregation.group(key1).avg(key2).as("r");
            case MAX:
                return Aggregation.group(key1).max(key2).as("r");
            case MIN:
                return Aggregation.group(key1).min(key2).as("r");
            case SUM:
                return Aggregation.group(key1).sum(key2).as("r");
            case COUNT:
                return Aggregation.group(key1).count().as("r");
            default:
                return null;
        }
    }
}
